在開始設計 API 模型之前,我會將模型分成兩個成面來做介紹,一是程式設計下的模型(Model),再來是API需求設計下的模型。
今天我們先來了解一下程式設計下的Model,並且瞭解 Resource-Based API 設計,這是一個理解 API 模型的重要概念,API需求設計下的模型則會留到明天再繼續。
在 API 設計中,「資源」(Resource)和「資料」(Data)是兩個非常重要的概念。它們聽起來相似,甚至從中文的角度來看更容易讓人混淆,但在實際應用中有著明顯的區別。理解這兩者之間的差異,對於我們設計 API 模型至關重要。
假設一個情境:今天你需要開發一個功能,這個功能要求取得客戶的資料,並根據客戶的等級來決定推送的訊息。我們可以先從資料庫中獲取客戶的資料,然後再進行處理。然而,如果不仔細設計,這樣的操作可能會帶來一系列問題。
我們先用 EntityCustomer
來假設從資料庫中生成的表:
//假設是從資料庫自動生成的
public class EntityCustomer
{
public string CustomerID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int CustomerLevel { get; set; }
public string PasswordHash { get; set; }
}
再來看看取得資料的 API 程式:
public EntityCustomer GetCustomer(string CustomerID)
{
var result = db.EntityCustomer.FirstOrDefault(x => x.CustomerID == CustomerID);
return result;
}
執行後,我們獲取到的原始資料可能是這樣的:
接著,在前端或其他邏輯層中處理這些資料,將 CustomerLevel
轉換成具體的描述,如「黃金級客戶」。
這樣的實現方式看似清晰簡單,但問題在於:這支 API 把不必要的資料也取出來了,尤其是高度機密的客戶密碼。而且,如果今天資料庫中的 EntityCustomer
表做了更改,比如將 CustomerLevel
從 int
改為 string
,前端或其他邏輯層就必須同步修改程式。這樣的設計顯然存在潛在的風險和問題。
CustomerID
、Name
、Email
、PasswordHash
、Address
等等。這些字段代表了原始的、未經處理的數據。Customer
資源可能只包含 CustomerID
、Name
和 Email
,而不包括 PasswordHash
或其他敏感信息。這樣設計的目的是保護資料的安全性,同時提供用戶所需的數據。在設計 API 時,直接從資料庫取出資料並回傳,可能會引發以下問題:
PasswordHash
)暴露給外部使用者,這會帶來嚴重的安全風險。Status
字段可能存儲的是 "Pending",但在中文界面中需要顯示為「待處理」。這種情況下,API 模型可以根據用戶的語言偏好來動態生成不同語言的回應。為了解決上述問題,我們應該在設計 API 模型時,將資料和資源分離,通過模型的設計來控制 API 回傳的內容。這樣不僅可以防止資料庫結構的變動影響 API,還能避免不必要或敏感數據的泄露。
在設計 API 的模型時,我們可以有意識地過濾掉敏感數據。例如,在設計用戶資源時,我們可以僅返回非敏感的字段,而不返回 PasswordHash
或 SocialSecurityNumber
這類敏感信息。
public class CustomerForAPI
{
public string CustomerID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string FormattedLevel { get; set; }
}
這樣設計的好處是,即使資料庫中的表包含敏感數據,也不會通過 API 暴露出去,保護了用戶的隱私和系統的安全性。
通過設計獨立於資料庫模型的 API 模型,我們可以將資料庫的結構變更對 API 的影響降到最低。即使資料庫結構發生變更,例如字段名稱或數據類型的變更,我們只需要修改資料庫到 API 模型的映射邏輯,而不必改變 API 的外部接口。
這種解耦使得 API 更加穩定和可維護。
在不同的業務場景中,客戶端對資源的需求可能不同。有時候一個完整的資源模型可能過於冗長,導致不必要的數據傳輸。因此,我們可以設計多個不同的資源模型,根據不同的 API 端點或參數來決定返回哪種模型。
例如,我們可以根據業務需求,設計不同版本的用戶資源模型:
public class CustomerBasicResource
{
public string CustomerID { get; set; }
public string Name { get; set; }
public string FormattedLevel { get; set; }
}
public class CustomerDetailResource
{
public string CustomerID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Address { get; set; }
public int CustomerLevel { get; set; }
}
這樣,根據不同的業務需求,可以靈活地選擇返回詳細的資料或基本的資料,這既能滿足需求,也能提高效率。
在處理資源時,經常需要對資料進行轉換。例如,我們可以在 API 模型中將資料庫中的代碼轉換為用戶可讀的形式,或根據用戶語言進行動態翻譯。這樣的設計不僅提升了用戶體驗,還能讓 API 更加靈活和智能。
public class CustomerForAPI
{
public string CustomerID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string FormattedLevel { get; set; }
}
直接讓API回傳的結果是轉變過的資料,FormattedLevel 怎麼從CustomerLevel去做轉換的這邊先不細談,有很多種方式去做轉換,根據實際的開發使用情境用其最適合的方式即可。這樣的模型設計可以讓 API 更加靈活和適應不同的用戶需求,提升系統的適應性。
那麼想要設計WebAPI模型,我們應該怎麼做最好,我會推薦初學者從MVC與MVVM架構去做延伸。
MVC(Model-View-Controller)是一種經典的設計模式,用於組織應用程序的結構。它將應用分成三個主要部分:
Model:
Model 是應用程序的數據層,負責處理業務邏輯和與數據庫的交互。它包含應用程序的核心數據,以及操作這些數據的方法。
View:
View 是應用程序的表示層,負責展示數據給用戶。它從 Model 獲取數據,並將這些數據渲染為用戶可以看到和交互的界面。
Controller:
Controller 是應用程序的邏輯層,負責處理用戶的輸入並更新 Model 和 View。Controller 是 Model 和 View 之間的橋樑,將用戶的輸入轉換為對 Model 的操作,並將 Model 的變化通知 View。
MVC 的優點是將應用程序的不同方面分離開來,使得代碼更加模塊化、可維護性強。
MVVM(Model-View-ViewModel)是對 MVC 模式的一種改進,主要用於前端開發,特別是在使用數據綁定技術的框架中(如 WPF 或 Vue.js)。MVVM 將 先不處理控制的部分,而是聚焦於資料的邏輯處理。
Model:
與 MVC 中的 Model 類似,負責業務邏輯和數據操作。
View:
與 MVC 中的 View 類似,負責界面的展示。
ViewModel:
ViewModel 是一個與 View 緊密相關的 Model,它包含了 View 所需的所有數據和邏輯。ViewModel 通常包含一些屬性和方法,這些屬性和方法會直接綁定到 View 中的控件。當 View 中的數據發生變化時,ViewModel 會自動更新,反之亦然。
MVVM 的優點在於它支持雙向數據綁定,這使得 UI 開發更加簡單和直觀。那這些架構該怎麼與API開發做結合呢?
在 Web API 的設計過程中,我們可以借鑒常見的應用程式設計模式,特別是 MVC(Model-View-Controller)和 MVVM(Model-View-ViewModel)這兩種模式,來確保我們的 API 結構清晰、可維護且具有高擴展性。這兩種模式在 Web 開發中非常常見,它們有助於將業務邏輯、數據展示和用戶輸入分離,從而提高系統的靈活性和穩定性。
讓我們來看看如何將這些模式與 Web API 的設計結合起來,並延伸出一些具體的應用。
在 Web API 設計中,MVC 模式經常被用來清晰地分離數據處理邏輯和表示邏輯,雖然 Web API 沒有直接的「View」,但我們可以將返回的資料視為 API 的「View」。MVC 的設計理念對 API 的架構也有很大的幫助,以下是 MVC 如何在 Web API 設計中發揮作用的細節:
int
轉換為具體的顯示內容,這樣即使數據庫模型改變,API 仍然可以保持穩定。// API 模型
public class CustomerResource
{
public string CustomerID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string CustomerLevel { get; set; } // 經過處理的資料
}
// 資料庫中的 Customer 資料表對應模型
public class Customer
{
public string CustomerID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int Level { get; set; } // 資料庫中存儲的等級是整數
}
在這裡,我們分成了兩個模型:
CustomerResource
:API 返回給前端的模型,它經過處理,不會直接暴露內部邏輯,包含用戶可讀的 CustomerLevel
。Customer
:從資料庫中獲取的原始資料模型,包含 Level
這樣的原始數據。// Service 負責業務邏輯
public interface ICustomerService
{
Customer GetCustomerById(string id);
}
public class CustomerService : ICustomerService
{
// 模擬一個從資料庫獲取客戶資料的方法
public Customer GetCustomerById(string id)
{
// 模擬資料庫查詢邏輯
var customers = new List<Customer>
{
new Customer { CustomerID = "1", Name = "王小明", Email = "WangXiouMing@gmail.com", Level = 1 },
new Customer { CustomerID = "2", Name = "李小花", Email = "LiXiaoHua@gmail.com", Level = 2 }
};
return customers.FirstOrDefault(c => c.CustomerID == id);
}
}
Service 層負責處理業務邏輯,並且從資料庫中獲取原始資料。這一層可以包含各種查詢或業務邏輯的實現,例如從資料庫查詢客戶信息、更新數據等。
// Controller 負責處理請求
[ApiController]
[Route("api/[controller]")]
public class CustomersController : ControllerBase
{
private readonly ICustomerService _customerService;
public CustomersController(ICustomerService customerService)
{
_customerService = customerService;
}
// 取得客戶資料
[HttpGet("{id}")]
public IActionResult GetCustomer(string id)
{
var customer = _customerService.GetCustomerById(id);
if (customer == null)
{
return NotFound();
}
// 將資料轉換為 API 模型
var resource = new CustomerResource
{
CustomerID = customer.CustomerID,
Name = customer.Name,
Email = customer.Email,
CustomerLevel = TranslateCustomerLevel(customer.Level) // 將等級轉換為可讀格式
};
return Ok(resource);
}
// 將客戶等級轉換為可讀的文字
private string TranslateCustomerLevel(int level)
{
return level switch
{
1 => "黃金客戶",
2 => "白金客戶",
_ => "普通客戶"
};
}
}
Controller 層負責接收 HTTP 請求,並調用 Service 層來獲取資料。當資料獲取後,Controller 將 Customer
資料轉換為 CustomerResource
,再回傳給前端。此處還有一個 TranslateCustomerLevel
方法,用於將等級數字轉換為可讀的字串。
在這個範例中,我們使用了 MVC 架構來設計 API,將數據的處理與控制能更佳邏輯清晰地分離開來,讓 Controller 專注於處理請求,並將數據經過 Model 過濾後返回給客戶端。但是這個範例還是有許多需要處理與調整的地方,因使我們可以接著更進階的設計。
MVVM 模式主要應用於前端開發,但它的理念對 API 設計也非常有幫助。MVVM 中的 ViewModel 讓我們可以將數據處理邏輯與呈現邏輯分開,這種思想在 API 設計中也非常有用,特別是在處理複雜數據轉換時。
這一部分代表資料庫中的實體,直接對應資料庫中的欄位,用來存儲和操作原始數據。
// Customer 資料庫模型
public class Customer
{
public string CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public int Level { get; set; } // 客戶等級
}
Model 負責映射資料庫的實體,這裡 Customer
是用來對應資料庫中存儲的客戶資料,包含 CustomerID
、FirstName
、LastName
、Email
和 Level
。
Service 層 負責業務邏輯和與資料庫的交互,處理客戶數據的查詢或修改。
// 服務接口
public interface ICustomerService
{
Customer GetCustomerById(string id);
}
// 服務實現
public class CustomerService : ICustomerService
{
// 模擬一個從資料庫獲取客戶資料的方法
public Customer GetCustomerById(string id)
{
// 模擬數據庫查詢邏輯
var customers = new List<Customer>
{
new Customer { CustomerID = "1", FirstName = "小明", LastName = "王", Email = "WangXiouMing@gmail.com", Level = 1 },
new Customer { CustomerID = "2", FirstName = "小花", LastName = "李", Email = "LiXiaoHua@gmail.com", Level = 2 }
};
return customers.FirstOrDefault(c => c.CustomerID == id);
}
}
Service 層負責與資料庫進行交互,通過 CustomerService
來模擬數據的獲取,例如 GetCustomerById()
方法會根據客戶 ID 查詢資料。
ViewModel 層 負責將數據從 Model 層轉換為適合展示的格式,並且根據需要進行處理,例如轉換客戶等級為多語言支持的格式。
// ViewModel:處理並轉換數據
public class CustomerViewModel
{
public string CustomerID { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
public string FormattedLevel { get; set; }
public string Language { get; set; }
public static CustomerViewModel FromCustomer(Customer customer, string language)
{
return new CustomerViewModel
{
CustomerID = customer.CustomerID,
DisplayName = $"{customer.FirstName} {customer.LastName}",
Email = customer.Email,
FormattedLevel = GetFormattedLevel(customer.Level, language),
Language = language
};
}
private static string GetFormattedLevel(int level, string language)
{
// 根據語言動態翻譯客戶等級
return language switch
{
"zh-TW" => level switch
{
1 => "黃金客戶",
2 => "白金客戶",
_ => "普通客戶"
},
_ => level switch
{
1 => "Gold Member",
2 => "Platinum Member",
_ => "Regular Member"
}
};
}
}
ViewModel 層 提供了轉換邏輯。CustomerViewModel
會將來自 Customer
的數據進行格式化處理,並支持多語言的客戶等級轉換。使用靜態方法 FromCustomer()
根據 Customer
資料和語言選擇來生成對應的 ViewModel
。
Controller 層 是應用程式的入口,負責處理 HTTP 請求,並通過調用 Service 層來獲取數據,最後將數據轉換為 ViewModel
回傳給用戶。
// API Controller
[ApiController]
[Route("api/[controller]")]
public class CustomersController : ControllerBase
{
private readonly ICustomerService _customerService;
public CustomersController(ICustomerService customerService)
{
_customerService = customerService;
}
// 使用 ViewModel 返回客戶資料
[HttpGet("{id}")]
public IActionResult GetCustomer(string id, [FromQuery] string language = "en-US")
{
var customer = _customerService.GetCustomerById(id);
if (customer == null)
{
return NotFound();
}
// 使用 ViewModel 將資料轉換為需要的格式
var viewModel = CustomerViewModel.FromCustomer(customer, language);
return Ok(viewModel);
}
}
Controller 層 負責接收 HTTP 請求,並且使用 Service 層的方法來查詢數據,然後將數據通過 ViewModel 進行轉換和處理。這裡的 GetCustomer()
方法會返回根據語言設定的 CustomerViewModel
給用戶。
這樣的分層設計確保了各個層次的責任單一,讓代碼更易於維護和擴展。在這個範例中,我們將資料經過 ViewModel 處理後再返回給客戶端。這裡的 ViewModel 扮演了資料轉換的角色,它根據客戶端的語言需求,將客戶等級動態翻譯為不同的語言形式。同時,ViewModel 還能進行一些更細緻的數據處理,例如將客戶的名字格式化為特定的展示格式。而在控制器的部分盡可能的簡潔,邏輯與資料的處理都交由其他部分做處理了。
通過結合 MVC 和 MVVM 的設計理念,我們可以在 Web API 開發中有效地將數據邏輯與呈現邏輯分開,使得系統更加模塊化、靈活並且易於維護。具體來說:
MVC 和 MVVM 架構在 Web API 設計中的應用,讓我們能夠更加靈活且有條理地管理數據處理邏輯和顯示邏輯。透過這些架構模式,我們可以確保 API 的數據輸出保持清晰、可靠並且具備高度的可擴展性。
MVC 模式強調將數據層(Model)與邏輯層(Controller)分開,並通過顯示層(View)來展現數據。而 MVVM 模式則讓我們在 API 設計中更靈活地處理數據,特別是在需要進行複雜的資料轉換、語言翻譯和格式化時,ViewModel 提供了一個更加有力的工具。
總結來說,結合 MVC 和 MVVM 的設計思想,我們可以更好地設計和實現高效且可維護的 Web API,使其具備靈活性、適應性並能夠輕鬆應對複雜的業務需求和不斷變化的數據結構。
今日小結:
今天我們深入探討了程式設計下的 API 模型設計,特別是 Resource-Based API 的概念。通過區分「資料」與「資源」,我們了解到為何在設計 API 時,應避免直接暴露資料庫中的原始資料,而應該通過模型來過濾、轉換資料,以保護敏感數據並提高 API 的靈活性與可維護性。
此外,我們也介紹了 MVC 和 MVVM 架構,這些架構為我們提供了設計 API 模型的良好參考。透過將資料庫模型和 API 模型分離,並根據業務需求動態生成合適的回應,我們能夠構建出更加穩定、可擴展的 API 系統。
明天,我們將繼續探討 API 需求設計下的模型,進一步深入如何為不同的業務場景設計正確的 API。